Skip to content

SNI extension#105

Merged
markrwilliams merged 13 commits intopython-tls:masterfrom
ashfall:sni-extension
Nov 26, 2016
Merged

SNI extension#105
markrwilliams merged 13 commits intopython-tls:masterfrom
ashfall:sni-extension

Conversation

@ashfall
Copy link
Copy Markdown
Contributor

@ashfall ashfall commented Nov 17, 2016

Implementing as specified in https://tools.ietf.org/html/rfc6066#section-3

{
enums.NameType.HOST_NAME: HostName
}
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps EnumSwitch would go better here

Copy link
Copy Markdown
Member

@markrwilliams markrwilliams Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

EDIT I just realized that comment wasn't clear -- sorry! I meant to say that it's OK to make this use EnumSwitch in this PR, but I'm happy to see this addressed in a separate PR that closes #106.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 412e913 on ashfall:sni-extension into 67c2ef3 on pyca:master.

1 similar comment
@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage remained the same at 100.0% when pulling 412e913 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 406f198 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage remained the same at 100.0% when pulling f3663a6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

1 similar comment
@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling f3663a6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling f3663a6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@ashfall
Copy link
Copy Markdown
Contributor Author

ashfall commented Nov 17, 2016

I'm not very happy about doing server_name_list[0].name.data and would rather 'localhost' be server_name_list[0].name, i.e. not have the HostName container as a thing. From the RFC (https://tools.ietf.org/html/rfc6066):

      struct {
          NameType name_type;
          select (name_type) {
              case host_name: HostName;
          } name;
      } ServerName;

      enum {
          host_name(0), (255)
      } NameType;

      opaque HostName<1..2^16-1>;

      struct {
          ServerName server_name_list<1..2^16-1>
      } ServerNameList;

Thoughts?

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling df5c174 on ashfall:sni-extension into 67c2ef3 on pyca:master.

Copy link
Copy Markdown
Member

@markrwilliams markrwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I addressed your comment in my review.

Otherwise this looks great! I'm looking forward to the next review.

Thanks so much for getting extension parsing working!!

{
enums.NameType.HOST_NAME: HostName
}
)
Copy link
Copy Markdown
Member

@markrwilliams markrwilliams Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

EDIT I just realized that comment wasn't clear -- sorry! I meant to say that it's OK to make this use EnumSwitch in this PR, but I'm happy to see this addressed in a separate PR that closes #106.

Array(lambda ctx: ctx.length, UBInt8("compression_methods"))
)

HostName = Struct(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From @ashfall's comment on this PR:

I'm not very happy about doing server_name_list[0].name.data and would rather 'localhost' be server_name_list[0].name, i.e. not have the HostName container as a thing.

I think you can remove the Struct and name the PrefixedBytes hostname:

HostName = PrefixedBytes("hostname", UBInt16("length"))

But I haven't tested it.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 17, 2016

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 085f5d6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

2 similar comments
@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 085f5d6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 085f5d6 on ashfall:sni-extension into 67c2ef3 on pyca:master.

Copy link
Copy Markdown
Member

@markrwilliams markrwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question and a docstring spelling issue.

Thanks so much for working on this!

:func:`parse_server_hello` returns an instance of :class:`ServerHello`
with extensions, when the extension bytes are present in the input.
:func:`parse_server_hello` fails to parse when
SIGNATURE_ALGORITHMS extention bytes are present in the packet
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the docs build failure is due to extention:

https://travis-ci.org/pyca/tls/jobs/176794154#L433

:return: ClientHello object.
"""
construct = _constructs.ClientHello.parse(bytes)
if any(extension.type not in cls.allowed_extensions
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an excellent catch! Should as_bytes also have this check, so that it's not possible to serialize a hello message that contains invalid extensions?

@ashfall
Copy link
Copy Markdown
Contributor Author

ashfall commented Nov 20, 2016

@markrwilliams Thanks for the review, updates coming!

I have a question for you about the EnumSwitch API. In order to satisfy the coverage requirement, I was writing a test with an unsupported extension in ClientHello:

    def test_client_hello_fails_with_unsupported_extension(self):
        """
        :py:func:`tls.hello_message.ClientHello` does not parse a packet
        with an unsupported extension, and raises an error.
        """
        server_certificate_type_extension_data = (
            b'\x00\x14'  # Extension Type: Server Certificate Type
            b'\x00\x00'  # Length
            b''  # Data
        )

        client_hello_packet = self.common_client_hello_data + (
            b'\x00\x04'  # extensions length
        ) + server_certificate_type_extension_data

        with pytest.raises(UnsupportedExtensionException):
            ClientHello.from_bytes(
                client_hello_packet
            )

Since we haven't implemented the server_certificate_type extension yet, Extension raised a SwitchError: no default case defined on parsing server_certificate_type_extension_data.

To define a default, EnumSwitch says:

    :param value_choices: A dictionary that maps members of
        `type_enum` to subconstructs.  This follows
        :py:func:`construct.core.Switch`'s API, so ``_default_`` will
        match any members without an explicit mapping.
    :type value_choices: :py:class:`dict`

and it was unclear to me how/where to provide the __default__.

To make my test pass, I hacked around a bit and made the following changes:

I modified EnumSwitch slightly, a rough implementation is below (the if-else is ugly, I should clean that up):

def EnumSwitch(type_field, type_enum, value_field, value_choices, default=None): 
    if not default:  # we don't want to overwrite Switch's default NoDefault (https://github.com/construct/construct/blob/master/construct/core.py#L1550)
        return (EnumClass(type_field, type_enum),
                construct.Switch(value_field,
                                 operator.attrgetter(type_field.name),
                                 value_choices))
    else:  
        return (EnumClass(type_field, type_enum),
                construct.Switch(value_field,
                                 operator.attrgetter(type_field.name),
                                 value_choices,
                                 default=default))

And added a default construct.Pass to the Extension construct:

Extension = Struct(
    "extensions",
    *EnumSwitch(
        type_field=UBInt16("type"),
        type_enum=enums.ExtensionType,
        value_field="data",
        value_choices={
            enums.ExtensionType.SERVER_NAME: Opaque(ServerNameList),
            enums.ExtensionType.SIGNATURE_ALGORITHMS: Opaque(
                SupportedSignatureAlgorithms
            ),
        },
        default=Pass,
    )
)

This made test_client_hello_fails_with_unsupported_extension pass.

All the hackery aside, is there an existing way to provide default values to EnumSwitch?

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 20, 2016

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 9d1e243 on ashfall:sni-extension into 67c2ef3 on pyca:master.

3 similar comments
@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 9d1e243 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 9d1e243 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.08%) to 99.924% when pulling 9d1e243 on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 20, 2016

Coverage Status

Coverage decreased (-0.08%) to 99.925% when pulling 1edcbed on ashfall:sni-extension into 67c2ef3 on pyca:master.

@coveralls
Copy link
Copy Markdown

coveralls commented Nov 23, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 7c77cc8 on ashfall:sni-extension into 5d48cc4 on pyca:master.

@markrwilliams
Copy link
Copy Markdown
Member

@ashfall This looks great! I'm going to merge. We can address any lingering concerns in subsequent PRs.

Thanks so much for implementing this!

@markrwilliams markrwilliams merged commit 5a7a0ec into python-tls:master Nov 26, 2016
@ashfall ashfall deleted the sni-extension branch December 9, 2016 07:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants